package org.jflame.commons.crypto;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;

import org.jflame.commons.codec.Transcoder;
import org.jflame.commons.convert.mutual.MutualConverter;
import org.jflame.commons.util.IOHelper;
import org.jflame.commons.util.StringHelper;
import org.jflame.commons.valid.Validators;

/**
 * 加解密抽象基类
 *
 * @author zyc
 */
public abstract class BaseEncryptor {

    private String providerName;
    protected final Transformation transformation;
    /**
     * 明文字符集
     */
    private Charset charset = StandardCharsets.UTF_8;
    /**
     * 编码器,对加解密后的数据编码
     */
    private MutualConverter<String,byte[]> cihperTranscoder = new Base64Converter();

    public BaseEncryptor(String transformation) throws EncryptException {
        this(Transformation.parse(transformation), null);
    }

    public BaseEncryptor(Transformation tf) throws EncryptException {
        this(tf, null);
    }

    public BaseEncryptor(Transformation tf, String providerName) throws EncryptException {
        Validators.requireNonNull(tf, "Transformation must not be null");
        transformation = tf;
        if (providerName != null) {
            if ("BC".equals(providerName)) {
                BC.addProvider();
            } else if (Security.getProvider(providerName) == null) {
                throw new EncryptException(providerName + " provider is not installed");
            }
            this.providerName = providerName;
        }
        if (!isSupport()) {
            throw new EncryptException("不支持的算法:" + tf.toString());
        }
    }

    /**
     * 是否支持的算法
     */
    public abstract boolean isSupport();

    /**
     * 执行数据加解密操作
     * 
     * @param data 待加解密数据
     * @param param 加解密需要的参数,如果密钥、向量等
     * @param mode 加密还是解密模式, 取值:Cipher.ENCRYPT_MODE,Cipher.DECRYPT_MODE
     * @return
     */
    public byte[] doProcess(final byte[] data, final CipherArgs param, final int mode) throws EncryptException {
        Cipher cipher = newCipher(param, mode);
        try {
            byte[] cipherText = cipher.doFinal(data);
            return cipherText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new EncryptException(mode == Cipher.ENCRYPT_MODE ? "加密异常" : "解密异常", e);
        }
    }

    /**
     * 执行流的加解密操作
     * 
     * @param inStream 输入流，需要加/解密的流
     * @param outStream 输出流，计算结果输出流
     * @param param 加解密需要的参数,如果密钥、向量等
     * @param mode 加密还是解密模式, 取值:Cipher.ENCRYPT_MODE,Cipher.DECRYPT_MODE
     * @throws EncryptException
     */
    public void doProcess(final InputStream inStream, final OutputStream outStream, final CipherArgs param,
            final int mode) throws EncryptException {
        Cipher cipher = newCipher(param, mode);
        if (mode == Cipher.ENCRYPT_MODE) {
            CipherInputStream cipherInputStream = null;
            try {
                // 以加密流写入文件
                cipherInputStream = new CipherInputStream(inStream, cipher);
                IOHelper.copy(cipherInputStream, outStream);
            } catch (IOException e) {
                throw new EncryptException("文件加密异常", e);
            } finally {
                IOHelper.closeQuietly(cipherInputStream);
                IOHelper.closeQuietly(outStream);
            }
        } else {
            CipherOutputStream cipherOutputStream = null;
            try {
                cipherOutputStream = new CipherOutputStream(outStream, cipher);
                IOHelper.copy(inStream, cipherOutputStream);
            } catch (IOException e) {
                throw new EncryptException("文件解密异常", e);
            } finally {
                IOHelper.closeQuietly(cipherOutputStream);
                IOHelper.closeQuietly(inStream);
            }
        }
    }

    /**
     * 加密
     * 
     * @param data 待加解密数据
     * @param param 加解密需要的参数,如果密钥、向量等
     * @return 密文
     * @throws EncryptException
     */
    public final byte[] encrypt(final byte[] data, final CipherArgs param) throws EncryptException {
        return doProcess(data, param, Cipher.ENCRYPT_MODE);
    }

    /**
     * 加密字符串
     * 
     * @param data 待加密字符串
     * @param param 算法需要的参数,如果密钥、向量等
     * @return 密文
     * @throws EncryptException
     */
    public String encrypt(final String data, final CipherArgs param) throws EncryptException {
        if (StringHelper.isEmpty(data)) {
            return data;
        }
        byte[] rawBytes = encrypt(data.getBytes(getCharset()), param);
        return encodeCipher(rawBytes);
    }

    /**
     * 加密文件
     * 
     * @param srcFilePath 待加密文件路径
     * @param cipherFilePath 加密后文件路径，加密数据写入该文件
     * @param param 算法需要的参数,如果密钥、向量等
     * @throws EncryptException
     */
    public void encrypt(final Path srcFilePath, final Path cipherFilePath, final CipherArgs param)
            throws EncryptException {
        try {
            doProcess(Files.newInputStream(srcFilePath), Files.newOutputStream(cipherFilePath), param,
                    Cipher.ENCRYPT_MODE);
        } catch (IOException e) {
            throw new EncryptException(e);
        }
    }

    /**
     * 解密
     * 
     * @param data 待解密数据
     * @param param 解密需要的参数,如果密钥、向量等
     * @return 明文
     * @throws EncryptException
     */
    public final byte[] decrypt(final byte[] data, final CipherArgs param) throws EncryptException {
        return doProcess(data, param, Cipher.DECRYPT_MODE);
    }

    /**
     * 解密字符串
     * 
     * @param cipherText 待解密字符串
     * @param param 解密需要的参数,如果密钥、向量等
     * @return 明文
     * @throws EncryptException
     */
    public final String decrypt(final String cipherText, final CipherArgs param) throws EncryptException {
        if (StringHelper.isEmpty(cipherText)) {
            return cipherText;
        }
        byte[] cipherBytes = decodeCipherText(cipherText);
        byte[] plainBytes = decrypt(cipherBytes, param);
        return new String(plainBytes, charset);
    }

    /**
     * 解密文件
     * 
     * @param cipherFilePath 加密文件路径
     * @param plainFilePath 明文文件存放路径
     * @param param 解密需要的参数,如果密钥、向量等
     * @throws EncryptException
     */
    public void decrypt(final Path cipherFilePath, final Path plainFilePath, final CipherArgs param)
            throws EncryptException {
        try {
            doProcess(
                    Files.newInputStream(cipherFilePath), Files.newOutputStream(plainFilePath,
                            StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING),
                    param, Cipher.DECRYPT_MODE);
        } catch (IOException e) {
            throw new EncryptException(e);
        }
    }

    protected Cipher newCipher(final CipherArgs param, final int mode) throws EncryptException {
        Cipher cipher = null;
        try {
            if (StringHelper.isNotEmpty(providerName)) {
                cipher = Cipher.getInstance(transformation.toString(), providerName);
            } else {
                cipher = Cipher.getInstance(transformation.toString());
            }

            if (param.getInitParams() != null) {
                if (!transformation.isNeedIV() && param.getInitParams() instanceof IvParameterSpec) {
                    cipher.init(mode, param.getKey(mode));
                } else {
                    cipher.init(mode, param.getKey(mode), param.getInitParams());
                }
            } else {
                cipher.init(mode, param.getKey(mode));
            }
            return cipher;
        } catch (InvalidKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException
                | NoSuchPaddingException e) {
            throw new EncryptException("初始加密算法失败" + transformation.toString(), e);
        } catch (NoSuchProviderException e) {
            throw new EncryptException("未找到名为" + providerName + "的Provider", e);
        }

    }

    /**
     * 加密后的数据编码为字符串
     * 
     * @see #cihperTranscoder
     * @param cipherBytes
     * @return
     */
    String encodeCipher(byte[] cipherBytes) {
        return cihperTranscoder.inverseConvert(cipherBytes);
    }

    /**
     * 解码 编码后的加密字符串
     * 
     * @see #cihperTranscoder
     * @param str
     * @return byte[]
     */
    byte[] decodeCipherText(String str) {
        return cihperTranscoder.convert(str);
    }

    public final Transformation getTransformation() {
        return transformation;
    }

    /**
     * 设置是否url安全,使用base64编码时可用
     * 
     * @param isUrlSafe
     */
    public final void setBase64UrlSafe(boolean isUrlSafe) {
        if (cihperTranscoder instanceof Base64Converter) {
            ((Base64Converter) cihperTranscoder).setUrlSafe(isUrlSafe);
        }
    }

    public void setCihperTranscoder(MutualConverter<String,byte[]> stringTranscoder) {
        this.cihperTranscoder = stringTranscoder;
    }

    /**
     * 启用hex编码器
     */
    public void enableHexEncode() {
        if (cihperTranscoder instanceof HexConverter) {
            return;
        }
        this.cihperTranscoder = new HexConverter();
    }

    public Charset getCharset() {
        return charset;
    }

    /**
     * 设置明文的字符集
     * 
     * @param charset
     */
    public void setCharset(Charset charset) {
        this.charset = charset;
    }

    public String getProviderName() {
        return providerName;
    }

    class HexConverter implements MutualConverter<String,byte[]> {

        @Override
        public String inverseConvert(byte[] source) {
            return Transcoder.encodeHexString(source);
        }

        @Override
        public byte[] convert(String source) {
            return Transcoder.decodeHex(source);
        }
    }

    class Base64Converter implements MutualConverter<String,byte[]> {

        private boolean urlSafe = true;

        public Base64Converter() {
        }

        public Base64Converter(boolean urlSafe) {
            this.urlSafe = urlSafe;
        }

        @Override
        public String inverseConvert(byte[] source) {
            return urlSafe ? Transcoder.encodeBase64URLSafeString(source) : Transcoder.encodeBase64String(source);
        }

        @Override
        public byte[] convert(String source) {
            return urlSafe ? Transcoder.decodeBase64URLSafeString(source) : Transcoder.decodeBase64(source);
        }

        public void setUrlSafe(boolean urlSafe) {
            this.urlSafe = urlSafe;
        }
    }
}
